(ns clojure-demo.macro2
  (:use [clojure.java.io]))

;;;;;;;;;
;8，宏卫生（符号冲突问题，重复求值问题）

;----
;8.1，符号冲突问题

;在宏里面使用的某个符号名，跟外部代码，或者传入的用户自定义代码里的某个符号名字，可能会发生冲突。

(defmacro   unhygienic
            [& body]
            `(let [x :oops]
                  ~@body))
;= #'user/unhygenic

(unhygienic (println "x:" x))
;= #<CompilerException java.lang.RuntimeException:
;= Can't let qualified name: user/x, compiling:(NO_SOURCE_PATH:1)>

(macroexpand-1 `(unhygienic (println "x:" x)))
;= (clojure.core/let [user/x :oops]
;= (clojure.core/println "x:" user/x))

;所有对x的引用都被扩展成user/x，但是let需要的名字是没有命名空间限定的。
;可以通过引述、反引述来“聪明”的避免这个错误，

(defmacro   still-unhygienic
            [& body]
            `(let [~'x :oops] 
                  ~@body))
;= #'user/still-unhygenic

(still-unhygienic (println "x:" x))
; x: :oops
;= nil

(macroexpand-1 '(still-unhygienic
                    (println "x:" x)))
;= (clojure.core/let [x :oops]
;= (println "x:" x))

;但这是给代码引入一个重大的bug。

(let [x :this-is-important]
     (still-unhygienic
        (println "x:" x)))
; x: :oops

;这里已经把x绑定到一个本地值了，但是由宏产生的let会悄悄把x绑定到另外一个值。

;----
;用gensym函数来生成一个保证唯一的符号。

(gensym)
;= G__2386

(gensym)
;= G__2391

(gensym "sym")
;= sym2396

(gensym "sym")
;= sym2402

;在语法引述形式里面，任何以#结尾的符号都会被自动扩展，
;并且对于前缀相同的符号，它们会被扩展成同一个符号的名字，这成为“自动gensym”。
;但是，只在同一个语法引述形式里所产生的符号名字是一样的。

`(x# x#)
;= (x__1447__auto__ x__1447__auto__)

(defmacro   auto-gensyms
            [& numbers]
            `(let [x# (rand-int 10)] 
                  (+ x# ~@numbers))) 
;= #'user/auto-gensyms

(auto-gensyms 1 2 3 4 5)
;= 22

(macroexpand-1 '(auto-gensyms 1 2 3 4 5)) 
;= (clojure.core/let [x__570__auto__ (clojure.core/rand-int 10)]
;= (clojure.core/+ x__570__auto__ 1 2 3 4 5))

[`x# `x#]
;= [x__1450__auto__ x__1451__auto__]

(defmacro our-doto [expr & forms]
          `(let [obj# ~expr] 
                ~@(map (fn [[f & args]]
                           `(~f obj# ~@args)) 
                       forms) 
                obj#))

(our-doto   "It works"
            (println "I can't believe it"))
;= #<CompilerException java.lang.RuntimeException:
;= Unable to resolve symbol: obj__1456__auto__ in this context,
;= compiling:(NO_SOURCE_PATH:1)>

;在这种情况下，只能手工调用gensym。

(defmacro   our-doto [expr & forms]
            (let [obj (gensym "obj")]
                 `(let  [~obj ~expr]
                        ~@(map (fn [[f & args]]
                                   `(~f ~obj ~@args)) 
                               forms) 
                        ~obj)))

;当前，这里这个语法引述其实没什么意义，使用(list* f obj args)更好。

;----
;让宏的用户来选择名字

;一个宏如果故意向外部代码暴露出一个名字，则这个宏称为“不稳定的”。
;更好的方式是，让用户来选择这个绑定的名字。

(defmacro with
    [name & body]
    `(let [~name 5]
          ~@body))
;= #'user/with

(with bar (+ 10 bar))
;= 15

(with foo (+ 40 foo))
;= 45

